from itertools import chain

from sqlalchemy import types as T

from pdw.util import LazyProperty

import validators as V

class ModelMeta(type):

    def __init__(cls, name, bases, dct):
        cls.view = {}

class TopeModel(object):
    __metaclass__ = ModelMeta
    
    name = None
    validator = None

    def __init__(self, name, **kw):
        self.view = dict(self.__class__.view)
        self.name = name
        for k,v in kw.iteritems():
            setattr(self, k, v)

    @classmethod
    def add_view(cls, view_type, view_cls):
        cls.view[view_type] = view_cls

    def default_view(self):
        kw = dict(label=getattr(self, 'label', None),
                  help=getattr(self, 'help', None),
                  )
        return self.__class__.view['edit'](self, **kw)

    # SQLAlchemy integration
    def sa_colnames(self):
        return [ self.name ]

    def sa_columns(self, column_class, fk_class=None):
        return []

    def sa_update(self, obj, value):
        setattr(obj, self.name, value)

    # User input validation
    def to_python(self, value, state=None):
        return self.validator.to_python(value, state)

    def from_python(self, value, state=None):
        if self.validator:
            return self.validator.from_python(value, state)
        else:
            return value

class TopeGroup(TopeModel):

    def __init__(self, name, **kw):
        TopeModel.__init__(self, name, **kw)
        self.subtopes = dict((st.name, st) for st in self.fields)
        cls = self.__class__
        if cls.validator is not None:
            chained_validators = [ cls.validator, V.DictMaker() ]
        else:
            chained_validators = [ V.DictMaker() ]
        self.validator = V.Schema(
            allow_extra_fields=True,
            # ignore_key_missing=True,
            if_key_missing=None,
            # chained_validators=chained_validators,
            **dict((k, v.validator) for k,v in self.subtopes.iteritems()))

    def __getitem__(self, name):
        return self.subtopes[name]

    def default_view(self):
        from clutch.topes.view import CompoundView
        return CompoundView(self)

    def sa_colnames(self):
        subtope_colnames = [ st.sa_colnames() for st in self.subtopes.itervalues() ]
        return list(chain(*subtope_colnames))

    def sa_columns(self, column_class, fk_class=None):
        subtope_cols = [ st.sa_columns(column_class)
                         for st in self.subtopes.itervalues() ]
        return list(chain(*subtope_cols))

    def sa_update(self, obj, value):
        for st in self.subtopes.itervalues():
            st.sa_update(obj, value.get(st))

class ColumnTope(TopeModel):
    extra_column_args = {}
    column_name = None
    _sa_column_type = None

    def __init__(self, name, **kw):
        TopeModel.__init__(self, name, **kw)
        if self.column_name is None:
            self.column_name = name

    @LazyProperty
    def sa_column_type(self):
        return self.column_type

    def sa_columns(self, column_class, fk_class=None):
        return [ column_class(self.column_name, self.sa_column_type,
                              **self.extra_column_args) ]

    def sa_update(self, obj, value):
        setattr(obj, self.name, value)

class ManyToOne(TopeModel):

    def __init__(self, name, relation_property, **kw):
        TopeModel.__init__(self, name, **kw)
        self.target_class = relation_property.property._get_target_class()
        self.validator = V.ModelValidator(self.target_class)

    def options(self):
        return [ (c.id, c.label) for c in self.target_class.query() ]

class OneToMany(TopeModel):

    def __init__(self, name, relation_property, child, **kw):
        TopeModel.__init__(self, name, **kw)
        self.target_class = relation_property.property._get_target_class()
        self.validator = V.ModelValidator(self.target_class)
        self.child = child

class StringTope(ColumnTope):
    min_length = None
    max_length = None
    validator_class = V.String
    extra_validator_args = {}

    def __init__(self, name, **kw):
        ColumnTope.__init__(self, name, **kw)

    @LazyProperty
    def validator(self):
        return self.validator_class(
            min=self.min_length, max=self.max_length,
            **self.extra_validator_args)

    @LazyProperty
    def sa_column_type(self):
        if self.max_length:
            return T.String(self.max_length)
        else:
            return T.Text()

class OneOf(ColumnTope):

    def __init__(self, name, options, type_=StringTope, **kw):
        ColumnTope.__init__(self, name, **kw)
        self.options = options
        self.type = type_(name, **kw)
        self.validator = self.type.validator

class EmailTope(StringTope):
    validator_class = V.Email
    

class UnicodeTope(StringTope):
    validator_class = V.UnicodeString

    @LazyProperty
    def sa_column_type(self):
        if self.max_length:
            return T.Unicode(self.max_length)
        else:
            return T.UnicodeText()

class RegexTope(UnicodeTope):
    validator_class = V.RegexValidator
    invalid_message = None
    regex=None

    def __init__(self, name, **kw):
        UnicodeTope.__init__(self, name, **kw)
        self.extra_validator_args['regex'] = self.regex
        self.validator.invalid_message = self.invalid_message
        
class StringRegexTope(StringTope):
    validator_class = V.RegexValidator
    invalid_message = None
    regex=None

    def __init__(self, name, **kw):
        StringTope.__init__(self, name, **kw)
        self.extra_validator_args['regex'] = self.regex
        self.validator.invalid_message = self.invalid_message
        
class SSNTope(StringRegexTope):
    regex='^\d{3}-\d{2}-\d{4}$'
    min_length=11
    max_length=11
    invalid_message = 'Please enter in the format ###-##-####'

class DateTope(ColumnTope):
    validator_class = V.DateConverter
    _sa_column_type = T.Date

class TimeTope(ColumnTope):
    validator_class = V.TimeConverter
    _sa_column_type = T.Date

class Number(ColumnTope):
    precision=None
    scale=None
    asdecimal=False
    min_value=None
    max_value=None

    ##@@ TODO: should I infer the min/max values based on precision and scale?

    @LazyProperty
    def validator(self):
        if self.scale == 0:
            base = V.Int
        else:
            base = V.Number
        return V.RangeBoundNumber(base, self.min_value, self.max_value)

    @LazyProperty
    def sa_column_type(self):
        args = dict(asdecimal=self.asdecimal)
        if self.precision is not None: args['precision'] = self.precision
        if self.scale is not None: args['scale'] = self.scale
        return T.Numeric(**args)

class Boolean(ColumnTope):
    validator_class=V.StringBool
    _sa_column_type = T.Boolean
